package com.codi.bus.core.service.impl;

import com.codi.base.exception.BaseAppException;
import com.codi.base.util.EqualsUtil;
import com.codi.base.util.ListUtil;
import com.codi.base.util.MoneyUtils;
import com.codi.base.util.StringUtil;
import com.codi.bus.constant.GlobalConstant;
import com.codi.bus.constant.PayTypeConst;
import com.codi.bus.constant.RuleConstant;
import com.codi.bus.core.dao.LookupDao;
import com.codi.bus.core.domain.BankLimit;
import com.codi.bus.core.domain.Lookup;
import com.codi.bus.core.domain.User;
import com.codi.bus.core.domain.UserBank;
import com.codi.bus.core.resultModel.BaseResult;
import com.codi.bus.core.resultModel.ShareListResult;
import com.codi.bus.core.service.*;
import com.codi.bus.core.service.model.PayMethodModel;
import com.codi.bus.core.service.model.ShareModel;
import com.codi.bus.core.service.model.WTradingRecord;
import com.codi.fundData.domain.SecurityProduct;
import com.codi.fundData.service.SecurityProductService;
import com.codi.message.constant.ErrorConstant;
import com.codi.message.util.MessageUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.map.HashedMap;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 模块名
 *
 * @author spy
 * @version 1.0 2017-12-01 13:32
 * @since 1.0
 */
@Slf4j
@Service
public class PaymentServiceImpl implements PaymentService {

    @Autowired
    private UserService userService;

    @Autowired
    private BankService bankService;

    @Autowired
    private WalletService walletService;

    @Autowired
    private LookupDao lookupDao;

    @Autowired
    private LookupService lookupService;

    @Autowired
    private BankLimitService bankLimitService;

    @Autowired
    private TradeOrderService tradeOrderService;

    @Autowired
    private SecurityProductService securityProductService;


    @Override
    @Transactional(readOnly = true)
    public BaseResult getPayMethods(Long userId) throws BaseAppException {
//        1.支付方式排列顺序依次：T+0基金（根据可用余额大小排序）>银行卡>线下汇款>不可用的支付方式；
//        2. 当买入金额未输入时，支付方式的额度与0比较，满足则可选择，不满足就为灰色，不可选择；
//        3. 当买入金额输入时，支付方式的额度与买入金额比较，满足则可选择，不满足就为灰色，不可选择；
//        4. 默认选中银行卡；
//        5. 显示5个，超过5个显示滚动条，可以上下滚动


        BaseResult result = new BaseResult(false);

        User userEntity = userService.getUser(userId);

        if (StringUtil.isEmpty(userEntity.getExternalClientId())) {
            result.setErrorCode(ErrorConstant.ERROR_NO_ACCOUNT);
            result.setErrorMessage(MessageUtil.getErrorMsg(ErrorConstant.ERROR_NO_ACCOUNT));
            return result;
        }

        result.setSuccess(true);

        List<UserBank> banks = bankService.getListByUserId(userId);
        if (CollectionUtils.isEmpty(banks)) {
            return result;
        }

        // 所有的支付方式
        List<PayMethodModel> payMethods = new ArrayList<>();


        // 可用的零钱包支持方式
        Map<String, Object> walletMap = payByWalletEnableShare(userId);

        List<PayMethodModel> payMethodsByWallet = (List<PayMethodModel>) walletMap.get("pays"); // 零钱包中可用的基金份额
        Collections.sort(payMethodsByWallet, new Comparator<PayMethodModel>() {
            @Override
            public int compare(PayMethodModel o1, PayMethodModel o2) {
                BigDecimal o1No = o1.getBalance(), o2No = o2.getBalance();
                return 0 - o1No.compareTo(o2No);
            }
        });
        payMethods.addAll(payMethodsByWallet);

        //Map<String, LinkedList<PayMethodModel>> taMap = (Map<String, LinkedList<PayMethodModel>>) walletMap.get("ta-map");

        //银行卡
        payMethods.addAll(payByBank(userId, banks));

        //添加一条线下汇款方式
        payMethods.add(addOneOffline(banks.get(0)).setPayMethodModels(payByOffline(banks)));

        //线下汇款
        //payMethods.addAll(payByOffline(banks));

        // 不可用支付方式
        // t0中没有可用份额的，进行补偿
        //payMethods.addAll(payByWalletDisabled(banks, taMap));

        //zx 2018年4月9日11:38:37  多卡支付新需求 要将相同的T0合并
        List<PayMethodModel> payMethodsNew = new ArrayList<>();
        List<String> fundCode = new ArrayList<>();
        BigDecimal walletBalance = new BigDecimal(0);
        for (PayMethodModel payMethodModel : payMethods) {
            //合并T0基金
            if ("T0".equals(payMethodModel.getPayType())){
                //如果已经加入新集合
                if (fundCode.contains(payMethodModel.getFundCode())){
                    for (PayMethodModel payMethodModelNew : payMethodsNew){
                        if (payMethodModel.getFundCode().equals(payMethodModelNew.getFundCode())){
                            payMethodModelNew.getPayMethodModels().add(payMethodModel);
                            walletBalance = MoneyUtils.add(walletBalance,payMethodModel.getBalance());
                            payMethodModelNew.setBalance(walletBalance);
                        }
                    }
                //如果没有加入新集合
                }else {
                    List<PayMethodModel> payMethodsInside = new ArrayList<>();
                    //因为 如果用原来的模型会导致无限循环  所以要新new一个
                    PayMethodModel payMethodModelCopy = new PayMethodModel();
                    BeanUtils.copyProperties(payMethodModel,payMethodModelCopy);
                    walletBalance = MoneyUtils.add(walletBalance,payMethodModelCopy.getBalance());
                    payMethodModelCopy.setPayMethodModels(payMethodsInside);
                    payMethodModelCopy.getPayMethodModels().add(payMethodModel);
                    payMethodsNew.add(payMethodModelCopy);
                    fundCode.add(payMethodModel.getFundCode());
                }
            }else {
                payMethodsNew.add(payMethodModel);
            }
        }

        payMethodsNew.forEach(payMethodModel -> {
            payMethodModel.setIsInside(false);
            if (payMethodModel.getPayMethodModels() != null){
                payMethodModel.getPayMethodModels().forEach(item -> {
                    item.setIsInside(true);
                });
            }
        });





        result.setSuccess(true);
        result.setResult(payMethodsNew);

        return result;
    }

    @Override
    @Transactional(readOnly = true)
    public BaseResult getPayMethodsForWallet(Long userId) {
        BaseResult result = new BaseResult(false);

        User userEntity = userService.getUser(userId);

        if (StringUtil.isEmpty(userEntity.getExternalClientId())) {
            result.setErrorCode(ErrorConstant.ERROR_NO_ACCOUNT);
            result.setErrorMessage(MessageUtil.getErrorMsg(ErrorConstant.ERROR_NO_ACCOUNT));
            return result;
        }

        List<UserBank> banks = bankService.getListByUserId(userId);

        result.setSuccess(true);

        if (CollectionUtils.isEmpty(banks)) {
            return result;
        }

        List<PayMethodModel> payMethods = new ArrayList<>(banks.size() * 2);


        //银行卡
        payMethods.addAll(payByBank(userId, banks));

        //添加一条线下汇款方式
        payMethods.add(addOneOffline(banks.get(0)).setPayMethodModels(payByOffline(banks)));

        payMethods.forEach(payMethodModel -> {
            payMethodModel.setIsInside(false);
            if (payMethodModel.getPayMethodModels() != null){
                payMethodModel.getPayMethodModels().forEach(item -> {
                    item.setIsInside(true);
                });
            }
        });

        //线下汇款
        //payMethods.addAll(payByOffline(banks));

        result.setResult(payMethods);
        return result;
    }

    @Override
    @Transactional(readOnly = true)
    public BaseResult getPayMethodsForPortfolio(Long userId) {
        BaseResult result = new BaseResult(false);

        User userEntity = userService.getUser(userId);

        if (StringUtil.isEmpty(userEntity.getExternalClientId())) {
            result.setErrorCode(ErrorConstant.ERROR_NO_ACCOUNT);
            result.setErrorMessage(MessageUtil.getErrorMsg(ErrorConstant.ERROR_NO_ACCOUNT));
            return result;
        }

        List<UserBank> banks = bankService.getListByUserId(userId);

        result.setSuccess(true);

        if (CollectionUtils.isEmpty(banks)) {
            return result;
        }

        List<PayMethodModel> payMethods = new ArrayList<>(banks.size());

        //银行卡
        payMethods.addAll(payByBank(userId, banks));

        result.setResult(payMethods);
        return result;
    }


    private Map<String, Object> payByWalletEnableShare(Long userId) {
        List<PayMethodModel> payMethodsByWallet = new ArrayList<>();

        ShareListResult sharesResult = walletService.queryWalletShareModels(userId);

        Map<String, LinkedList<PayMethodModel>> taMap = new HashedMap();

        if (sharesResult.getSuccess()) {
            List<ShareModel> shares = sharesResult.getShares();

            shares.forEach(share -> {
                String fullFundName = "", shortFundName = "";
                try {
                    SecurityProduct product = securityProductService.querySecurityProduct(share.getFundCode());
                    if (product != null) {
                        fullFundName = product.getChiNameAbbr();
                    }
                } catch (BaseAppException e) {
                    log.error("fail to query secu main", e);
                }
                if (fullFundName.toUpperCase().endsWith("A") || fullFundName.toUpperCase().endsWith("B")) {
                    shortFundName = fullFundName.substring(0, fullFundName.length() - 1);
                } else {
                    shortFundName = fullFundName;
                }


                PayMethodModel payMethodModel = PayMethodModel.builder()
                                                              .payType(PayTypeConst.T0)
                                                              .payName(PayTypeConst.T0_LABEL + " - " + fullFundName)
                                                              .fundCode(share.getFundCode())
                                                              .fundName(fullFundName)
                                                              .bankName(share.getBankName())
                                                              .bankNo(share.getBankAccount())
                                                              .capitalMode(share.getCapitalMode())
                                                              .tradeAcco(share.getTradeAccount())
                                                              .balance(share.getEnableShares())
                                                              .build();

                if (share.getEnableShares().compareTo(BigDecimal.valueOf(0)) == 0){
                    payMethodModel.setCanUse(false);
                }

                //TODO
                // t0中交易限制? 由目标基金限制

                LinkedList<PayMethodModel> funds = taMap.get(share.getTradeAccount());
                if (funds == null) {
                    funds = new LinkedList<>();
                    taMap.put(share.getTradeAccount(), funds);
                }
                funds.add(payMethodModel);

                payMethodsByWallet.add(payMethodModel);
            });

        } else {
            log.warn("fail to query user t0 shares");
        }

        //降序
        Collections.sort(payMethodsByWallet, (o1, o2) -> o2.getBalance().compareTo(o1.getBalance()));

        Map<String, Object> map = new HashMap<>();
        map.put("pays", payMethodsByWallet);
        map.put("ta-map", taMap);
        return map;
    }

    private List<PayMethodModel> payByWalletDisabled(List<UserBank> banks, Map<String, LinkedList<PayMethodModel>> taMap) {
        List<PayMethodModel> payMethodsByWalletDisabled = new ArrayList<>(); // 零钱包中可用的基金份额

        List<Lookup> walletFundLookups = lookupDao.getLookupByType(RuleConstant.RULE_LOOKUP_TYPE_T0FUND);

        // 总的T0列表
        Set<String> fundNames = new HashSet<>();
        for (Lookup lookup : walletFundLookups) {
            String fundName = lookup.getRuleText();
            fundNames.add(fundName.substring(0, fundName.length() - 1));
        }

        for (UserBank bank : banks) {

            if (EqualsUtil.equals(bank.getCapitalMode(), GlobalConstant.CAPITAL_MODE_COUNTER)) {
                continue;
            }

            String bankName = bank.getBankName();
            String bankNo = bank.getUserBankNo();
            String tradeAcco = bank.getExternalTradeAccount();

            LinkedList<PayMethodModel> walletFunds = taMap.get(tradeAcco);
            //clone
            Set<String> allFundNames = fundNames.stream().collect(Collectors.toSet());
            Set<String> partFundNames = new HashSet<>();
            if (ListUtil.isEmpty(walletFunds)) {
                walletFunds = new LinkedList<>();
            }
            // 差异补偿,只补偿A

            walletFunds.stream().forEach(payMethodModel -> {
                log.debug("payMethodModel={}", payMethodModel);

                String fundName = payMethodModel.getFundName();

                log.debug("fundName={}", fundName);

                if (fundName.toUpperCase().endsWith("A") || fundName.toUpperCase().endsWith("B")) {
                    fundName = fundName.substring(0, fundName.length() - 1);
                }
                partFundNames.add(fundName);
            });

            allFundNames.removeAll(partFundNames); //剩余需要补偿的

            for (Lookup lookup : walletFundLookups) {
                String fundCode = lookup.getRuleValue();
                String fundName = lookup.getRuleText();
                String fundNameShort = fundName.substring(0, fundName.length() - 1);
                if (!allFundNames.contains(fundNameShort)) {
                    continue;
                }

                if (fundName.toUpperCase().endsWith("A")) {
                    PayMethodModel payMethodModel = PayMethodModel.builder()
                                                                  .payType(PayTypeConst.T0)
                                                                  .payName(PayTypeConst.T0_LABEL + " - " + fundName)
                                                                  .fundCode(fundCode)
                                                                  .fundName(fundName)
                                                                  .bankName(bankName)
                                                                  .bankNo(bankNo)
                                                                  .tradeAcco(tradeAcco)
                                                                  .balance(BigDecimal.ZERO)
                                                                  .canUse(false)
                                                                  .build();

                    payMethodsByWalletDisabled.add(payMethodModel);
                }
            }
        }
        return payMethodsByWalletDisabled;
    }


    private List<PayMethodModel> payByBank(Long userId, List<UserBank> banks) {
        List<PayMethodModel> pays = new ArrayList<>(banks.size());

        User user = userService.getUser(userId);

        PayMethodModel payMethodModel;
        for (UserBank bank : banks) {

            if (EqualsUtil.equals(bank.getCapitalMode(), GlobalConstant.CAPTIAL_MODE_K)) {
                if (user.getIsOrg()) {
                    continue;
                }
            }

            if (EqualsUtil.equals(bank.getCapitalMode(), GlobalConstant.CAPITAL_MODE_COUNTER)) {
                continue;
            }

            payMethodModel = PayMethodModel.builder()
                                           .payType(PayTypeConst.BANK)
                                           .payName(bank.getBankName())
                                           .bankCode(bank.getBankCode())
                                           .bankName(bank.getBankName())
                                           .bankNo(bank.getUserBankNo())
                                           .capitalMode(bank.getCapitalMode())
                                           .tradeAcco(bank.getExternalTradeAccount())
                                           .canUse(true)
                                           .build();

            //查询该银行的限额
            BankLimit bankLimit = bankLimitService.selectByCodeAndChannel(bank.getBankCode(), bank.getCapitalMode());

            log.debug("bank limit ={}", bankLimit);

            if (bankLimit != null) {

                // 单笔限额max
                if (bankLimit.getSingleLimit() == null || bankLimit.getSingleLimit() == -1) {
                } else {
                    payMethodModel.setSingleDealMaxValue(BigDecimal.valueOf(bankLimit.getSingleLimit()));
                }

                BigDecimal limit;

                // 日限额 可用限额
                if (bankLimit.getDayLimit() == null || bankLimit.getDayLimit() == -1) {
                } else {
                    limit = BigDecimal.valueOf(bankLimit.getDayLimit());
                    payMethodModel.setDayDealMaxValue(limit);
                    BigDecimal daySum = tradeOrderService.getCurrentDayDealSum(bank.getExternalTradeAccount());

                    log.debug("ta={},daySum={}", bank.getExternalTradeAccount(), daySum);

                    payMethodModel.setDayDealRetainMaxValue(MoneyUtils.max(
                        MoneyUtils.subtract(limit, daySum), BigDecimal.ZERO)
                    );
                    if (MoneyUtils.isZero(payMethodModel.getDayDealRetainMaxValue())) {
                        payMethodModel.setCanUse(false);
                    }
                }

                // 月限额 可用限额
                if (bankLimit.getMonthLimit() == null || bankLimit.getMonthLimit() == -1) {
                } else {
                    limit = BigDecimal.valueOf(bankLimit.getMonthLimit());
                    payMethodModel.setMonthDealMaxValue(limit);
                    BigDecimal monthSum = tradeOrderService.getCurrentMonthDealSum(bank.getExternalTradeAccount());

                    log.debug("ta={},daySum={}", bank.getExternalTradeAccount(), monthSum);

                    payMethodModel.setMonthDealRetainMaxValue(MoneyUtils.max(
                        MoneyUtils.subtract(limit, monthSum), BigDecimal.ZERO
                    ));
                    if (MoneyUtils.isZero(payMethodModel.getDayDealRetainMaxValue())) {
                        payMethodModel.setCanUse(false);
                    }
                }
            }


            pays.add(payMethodModel);
        }
        return pays;
    }

    @Override
    public List<PayMethodModel> payByOffline(List<UserBank> banks) {
        List<PayMethodModel> pays = new ArrayList<>(banks.size());

        Lookup lookup = lookupService.getLookup(RuleConstant.TYPE_OFFLINE_BUY, RuleConstant.VALUE_OFFLINE_BUY_MIN_VALUE);
        String singleDealMinValue = "-1";
        if (lookup != null) {
            singleDealMinValue = lookup.getComponentValue();
        }

        PayMethodModel payMethodModel;
        for (UserBank bank : banks) {

            if (EqualsUtil.equals(bank.getCapitalMode(), GlobalConstant.CAPITAL_MODE_COUNTER)) {
                continue;
            }

            payMethodModel = PayMethodModel.builder()
                                           .payType(PayTypeConst.OFFLINE)
                                           .payName(PayTypeConst.OFFLINE_LABEL)
                                           .bankCode(bank.getBankCode())
                                           .bankName(bank.getBankName())
                                           .bankNo(bank.getUserBankNo())
                                           .capitalMode(bank.getCapitalMode())
                                           .tradeAcco(bank.getExternalTradeAccount())
                                           //单笔最小值
                                           .singleDealMinValue(new BigDecimal(singleDealMinValue))
                                           .canUse(true)
                                           .build();
            pays.add(payMethodModel);
        }
        return pays;
    }


    @Override
    public PayMethodModel addOneOffline(UserBank bank) {

        Lookup lookup = lookupService.getLookup(RuleConstant.TYPE_OFFLINE_BUY, RuleConstant.VALUE_OFFLINE_BUY_MIN_VALUE);
        String singleDealMinValue = "-1";
        if (lookup != null) {
            singleDealMinValue = lookup.getComponentValue();
        }

        PayMethodModel payMethodModel;
        payMethodModel = PayMethodModel.builder()
            .payType(PayTypeConst.OFFLINE)
            .payName(PayTypeConst.OFFLINE_LABEL)
            .bankCode(bank.getBankCode())
            .bankName(bank.getBankName())
            .bankNo(bank.getUserBankNo())
            .capitalMode(bank.getCapitalMode())
            .tradeAcco(bank.getExternalTradeAccount())
            //单笔最小值
            .singleDealMinValue(new BigDecimal(singleDealMinValue))
            .canUse(true)
            .build();
        return payMethodModel;
    }

}
